package errors

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"strconv"
)

// Error 错误
type Error struct {
	error
	*stack
	Code    int    // 错误码
	Message string // 消息
}

// Error 实现error接口
func (e Error) Error() string {
	b := e.prettifyOut()
	return b.String()
}

// Format 实现Formatter接口
func (e *Error) Format(s fmt.State, verb rune) {
	switch verb {
	case 'v':
		if s.Flag('+') {
			io.Copy(s, e.prettifyOut())
			return
		}
		fallthrough
	case 's', 'q':
		io.WriteString(s, e.Error())
	}
}

// prettifyOut 美化输出
func (e Error) prettifyOut() *bytes.Buffer {
	buf := bytes.Buffer{}

	if e.Code != 0 {
		buf.WriteString("code: ")
		buf.WriteString(strconv.Itoa(e.Code))
	}
	// 后续分隔符
	if e.Code != 0 && (e.error != nil || e.Message != "" || e.stack != nil) {
		buf.WriteString("; ")
	}

	if e.error != nil && e.Message != "" {
		buf.WriteString(e.Message)
		buf.WriteString(": ")
		buf.WriteString(e.error.Error())
	} else if e.error != nil {
		buf.WriteString(e.error.Error())
	} else if e.Message != "" {
		buf.WriteString(e.Message)
	}

	if e.stack != nil {
		fmt.Fprintf(&buf, "%+v", e.stack)
	}

	if buf.Len() == 0 {
		buf.WriteString("<nil>")
	}

	return &buf
}

// Nil 是否是nil
func (e *Error) Nil() error {
	switch {
	case e.Code != 0:
		return e
	case e.error != nil:
		return e
	case e.Message != "":
		return e
	case e.stack != nil:
		return e
	default:
		return nil
	}
}

// Equal 是否相等
func (e Error) Equal(err *Error) bool {
	if e.Code == err.Code {
		return true
	}
	if e.Message == err.Message {
		return true
	}
	return false
}

func (e *Error) Is(err error) bool {
	switch v := err.(type) {
	case *Error:
		return e.Equal(v)
	case Error:
		return e.Equal(&v)
	case error:
		return errors.Is(e.error, err)
	default:
		return false
	}
}

func (e *Error) Unwrap() error {
	if e.error == nil {
		return nil
	}
	return e.error
}

func (e *Error) Cause() error {
	if e.error == nil {
		return nil
	}
	return e.error
}

// GetCode 获取错误码
func (e Error) GetCode() int {
	return e.Code
}

// WithCode 设置错误码
func (e *Error) WithCode(code int) *Error {
	e.Code = code
	return e
}

// GetMessage 获取消息
func (e Error) GetMessage() string {
	return e.Message
}

// WithMessage 设置消息
func (e *Error) WithMessage(message string) *Error {
	e.Message = message
	return e
}

// WithStack 堆栈
func (e *Error) WithStack(skip int) *Error {
	e.stack = callers(skip)
	return e
}

// EnableStack 使能堆栈
func (e *Error) EnableStack() *Error {
	e.stack = callers(3)
	return e
}

// DisableStack 禁止堆栈
func (e *Error) DisableStack() *Error {
	e.stack = nil
	return e
}

func (e Error) MarshalText() ([]byte, error) {
	b := e.prettifyOut()
	return b.Bytes(), nil
}

// New 新建错误对象
func New(any interface{}, opts ...Option) *Error {
	if any == nil {
		return nil
	}

	var e *Error = nil

	switch v := any.(type) {
	case *Error:
		return v
	case Error:
		return &v
	case error:
		e = new(Error)
		e.error = v
	case string:
		e = new(Error)
		e.Message = v
	default:
		e = new(Error)
		e.Message = fmt.Sprintf("%v", v)
	}

	if i, ok := any.(IGetCode); ok {
		e.Code = i.GetCode()
	}
	if i, ok := any.(IGetMessage); ok {
		e.Message = i.GetMessage()
	}

	for _, opt := range opts {
		opt(e)
	}

	return e
}

func Errorf(format string, args ...interface{}) *Error {
	return &Error{
		error: fmt.Errorf(format, args...),
		stack: callers(3),
	}
}

func Wrap(err error, message string, opts ...Option) *Error {
	if err == nil {
		return nil
	}

	var e = new(Error)

	if i, ok := err.(IGetCode); ok {
		e.Code = i.GetCode()
	}

	for _, opt := range opts {
		opt(e)
	}

	e.error = err
	e.Message = message

	return e
}

func Wrapf(err error, format string, args ...interface{}) *Error {
	if err == nil {
		return nil
	}

	var e = &Error{
		error:   err,
		stack:   callers(3),
		Message: fmt.Sprintf(format, args...),
	}

	if i, ok := err.(IGetCode); ok {
		e.Code = i.GetCode()
	}

	return e
}

func WithStack(err error, opts ...Option) *Error {
	if err == nil {
		return nil
	}

	var e = new(Error)

	if i, ok := err.(IGetCode); ok {
		e.Code = i.GetCode()
	}

	for _, opt := range opts {
		opt(e)
	}

	e.error = err
	e.stack = callers(3)

	return e
}

func NewMessage(message string, opts ...Option) *Error {
	var e = new(Error)

	for _, opt := range opts {
		opt(e)
	}

	e.Message = message

	return e
}
